/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#define ALLOCATE_EXTERNS

#include <stdio.h>
#include "cc_compat.h"
#include "chip.h"
#include "io.h"
#include "adc.h"
#include "ac.h"
#include "power.h"
#include "timer.h"
#include "uart.h"
#include "sdi.h"
#include "kbc.h"
#include "ps2.h"
#include "monitor.h"
#include "keyboard.h"
#include "build.h"
#include "touchpad.h"
#include "onewirebatt.h"
#include "lifebatt.h"
#include "battery.h"
#include "gpio_smbus.h"
#include "charger.h"
#include "mppt.h"
#include "stop.h"
#include "every.h"
#include "ols.h"
#include "event.h"
#include "kbdwatch.h"
// Early init code
#ifdef SDCC
#define early_startup _sdcc_external_startup
#endif


#ifdef LJMP_WORKAROUND
/* BUG workaround:  it seemst that our 8051 core may have
 * trouble with this construct, in some cases:
 *
 *		   ljmp next
 *	next:  ...
 *
 * which wouldn't normally occur.  but Keil will place very
 * short routines in the middle of the interrupt vector area,
 * and if one of those routines is an interrupt service routine,
 * then it may directly follow the longjmp which targets it.
 * so to avoid this, we fill the vector area so the compiler
 * can't use it as regular codespace.
 */
#define UNUSED_IRQ(name, irq) void name(void) interrupt irq \
{ \
	_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); \
}

UNUSED_IRQ(ext0_interrupt,			0)
UNUSED_IRQ(timer0_interrupt,		1)
UNUSED_IRQ(ext1_interrupt,			2)
//UNUSED_IRQ(timer1_interrupt,		3)
//UNUSED_IRQ(serial0_interrupt,		4)
//UNUSED_IRQ(watchdog_interrupt,	5)
UNUSED_IRQ(n_a_0x33_interrupt,		6)
UNUSED_IRQ(n_a_0x3b_interrupt,		7)

UNUSED_IRQ(watchdog_interrupt,		8)
UNUSED_IRQ(n_a_0x4b_interrupt,		9)
//UNUSED_IRQ(ps2_interrupt,			10)
UNUSED_IRQ(kbc_interrupt,			11)
UNUSED_IRQ(rsv_0x63_interrupt,		12)
UNUSED_IRQ(lpc_interrupt,			13)
UNUSED_IRQ(ec_hi_interrupt,			14)
UNUSED_IRQ(n_a_0x7b_interrupt,		15)

UNUSED_IRQ(rsv_0x83_interrupt,		16)
UNUSED_IRQ(rsv_0x8b_interrupt,		17)
UNUSED_IRQ(rsv_0x93_interrupt,		18)
UNUSED_IRQ(n_a_0x9b_interrupt,		19)
UNUSED_IRQ(gpt0_interrupt,			20)
UNUSED_IRQ(gpt1_interrupt,			21)
UNUSED_IRQ(gpt2_interrupt,			22)
//UNUSED_IRQ(gpt3_interrupt,		23)

UNUSED_IRQ(extwio_port80_interrupt,	24)
// UNUSED_IRQ(intsrv_gpio00_0f,		25)
UNUSED_IRQ(gpio10_1b_interrupt,		26)
// UNUSED_IRQ(sdi_interrupt,		27)
// UNUSED_IRQ(intsrv_gpio30_38,		28)
UNUSED_IRQ(rsv_0xeb_interrupt,		29)
UNUSED_IRQ(rsv_0xf3_interrupt,		30)
//UNUSED_IRQ(adc_interrupt,			31)
#endif


unsigned char early_startup(void)
{
	// This is a _critical_ bit. Without it you can't
	// access xdata space.  xdata register access will not
	// work until this bit is set.
	PCON2 |= 0x10;

	IE = 0;

	// start the external oscillator.  we'll switch to it later.
	CRY32CR |= 0x01;

	// set the page number for pdata (high byte of 0xf400)
	P2 = 0xf4;

	return 0;   // return non-zero to skip clearing idata/xdata
}


#define LED_CYCLE_DELAY 90000
void cycle_leds(void)
{
	set_LED_PWR_on();
	usec_delay(LED_CYCLE_DELAY);
	set_LED_PWR_off();

	set_LED_CHG_RED_on();
	usec_delay(LED_CYCLE_DELAY);
	set_LED_CHG_RED_off();

	set_LED_CHG_GREEN_on();
	usec_delay(LED_CYCLE_DELAY);
	// Show amber
	set_LED_CHG_RED_on();
	usec_delay(LED_CYCLE_DELAY);
	// Back to green
	set_LED_CHG_RED_off();
	usec_delay(LED_CYCLE_DELAY);
	set_LED_CHG_GREEN_off();
	// Red
	set_LED_CHG_RED_on();
	usec_delay(LED_CYCLE_DELAY);
	set_LED_CHG_RED_off();

	set_LED_PWR_on();
	usec_delay(LED_CYCLE_DELAY);
	set_LED_PWR_off();

}

#define DEBUG_TRIM 0		// save trim reads, and dump with show_trim_dump()

// this next line will break things if trim_tune is called to early
#define DEBUG_TRIM_INLINE 0	// print applications of trim data as it happens

#if DEBUG_TRIM
unsigned char rt;
unsigned char rtsave[20];
unsigned int rtasave[20];
unsigned int rtisave[20];
#endif

unsigned char read_trim(unsigned short addr)
{
	unsigned char b;
	int i;

	XBIEFA1 = addr >> 8;
	XBIEFA0 = addr;
	XBIEFCMD = 0x90;  // Read Trim Data

	/* wait while flash is busy */
	i = 0;
	while ((XBIEFCFG & 0x80 ) == 0)
	{
		usec_delay(100);
		i++;
	}

	b = XBIEFDI;
#if DEBUG_TRIM
	rtisave[rt] = i;
	rtasave[rt] = addr;
	rtsave[rt++] = b;
#endif
	return b;
}

/* this routine based directly on sample code obtained from vendor */
void trim_tune(void)
{
	unsigned char b;

	// Previously this whole sequence was guarded by checking that the FIRMWARE_ID register
	// was equal to zero.  Thats the way the code was when it came from EnE.  Its not exactly
	// clear why but a possible explanation was that EnE only wanted the trim tune to run
	// once on power up.  In our implementation however, we never changed the FIRMWARE_ID register
	// so it stayed at zero and this ran every time the EC booted.  Running it every time
	// does not seem to cause any ill effects.  (Cross your fingers)

	// We now need to use the FIRMWARE_ID to be able to indicate automatic power up after a
	// reflash so the guard was removed.

	//Flash read Trim data and write to register (For ENE macros)
	if (read_trim(0x100) == 0x5A)
	{
		// LVD_Trim[4:0]
		LVD_TRIM = (LVD_TRIM & 0xe0) | (read_trim(0x101) & 0x1f);

		// Trim[3:0] of HKCOMOS32K
		IOSCCR = (IOSCCR & 0xf0) | (read_trim(0x102) & 0x0f);
	}

	//Flash read Trim data and write to register (For HHNEC macros)
	if (read_trim(0x1ff) != 0x00)
	{
		// S[4:0]
		b = (XBIMISC & 0xe0) | (read_trim(0x1f0) & 0x1f);
#if DEBUG_TRIM_INLINE
		printf("XBIMISC set to 0x%x\n", (uint)b);
#endif
		XBIMISC = b;

		// Pdac[3:0]
		b = (XBIPUMP & 0x0f) | ((read_trim(0x1f1) & 0x0f)<<4);
#if DEBUG_TRIM_INLINE
		printf("XBIPUMP set to 0x%x\n", (uint)b);
#endif
		XBIPUMP = b;

		// Ndac[3:0]
		b = (XBIPUMP & 0xf0) | (read_trim(0x1f2) & 0x0f);
#if DEBUG_TRIM_INLINE
		printf("XBIPUMP set to 0x%x\n", (uint)b);
#endif
		XBIPUMP = b;

		// Bdac[3:0]
		b = (XBIFM & 0xf0) | (read_trim(0x1f4) & 0x0f);
#if DEBUG_TRIM_INLINE
		printf("XBIFM set to 0x%x\n", (uint)b);
#endif
		XBIFM = b;

		// Tctrim[3:0]
		b = (XBIVR & 0x0f) | ((read_trim(0x1f5) & 0x0f)<<4);
#if DEBUG_TRIM_INLINE
		printf("XBIVR set to 0x%x\n", (uint)b);
#endif
		XBIVR = b;

		// Abstrim[3:0]
		b = (XBIVR & 0xf0) | (read_trim(0x1f6) & 0x0f);
#if DEBUG_TRIM_INLINE
		printf("XBIVR set to 0x%x\n", (uint)b);
#endif
		XBIVR = b;

		// Itim[3:0] (Note:Itim[3:0] must be the last one be updated)
		b = (XBIFM & 0x0f) | ((read_trim(0x1f3) & 0x0f)<<4);
#if DEBUG_TRIM_INLINE
		printf("XBIFM set to 0x%x\n", (uint)b);
#endif
		XBIFM = b;

		// Note:After Itim[3:0] is updated, it needs 3us
		// delay before any normal operations
		usec_delay(3);

		XBIEFCFG = 0x10;                           //Set flash clock
	}

#if DEBUG_TRIM_INLINE
	printf("firmware_id is 0x%x after tune\n", (uint)FIRMWARE_ID);
#endif

	if (XBIEFCFG == 0xd0)
	{
		printf("\nWARNING: XBIECFG  is 0xD0\n");
	}
}

#if DEBUG_TRIM
trim_dump()
{
	read_trim(0x0100);
	read_trim(0x0101);
	read_trim(0x0102);
	read_trim(0x01ff);
	read_trim(0x01f0);
	read_trim(0x01f1);
	read_trim(0x01f2);
	read_trim(0x01f3);
	read_trim(0x01f4);
	read_trim(0x01f5);
	read_trim(0x01f6);
}

// show all saved trim output
void show_trim_dump(void)
{
	unsigned char i;
	for (i = 0; i < rt; i++)
	{
		printf("trim 0x%04x is 0x%x after %d waits\n",
			   (uint)rtasave[i], (uint)rtsave[i], (uint)rtisave[i]);
	}
	rt = 0;
}

#endif

static void gpwu_init(void)
{
	char i;

	for (i = 0; i < 8; i++)
	{
		gpwu[GPWUEN00 + i] = 0;
		gpwu[GPWUPS00 + i] = 0;
		gpwu[GPWUEL00 + i] = 0;
		gpwu[GPWUCHG00 + i] = 0;
		gpwu[GPWUPF00 + i] = 0;
	}
}

void main (void)
{

	watchdog_stop();

	leds_init();

	/* loop for failsafe programmng.  shouldn't be necessary on
	 * next spin of chip, since trap pin (gpio4, pin 5) will hold
	 * us in reset.  */
	input_enable_RESTORE();
	while (read_RESTORE())
		cycle_leds();

	gpwu_init();

	uart_io_init();
	uart_init();

	EA = 1; // serial output does not work above this line.

	puts("!!! BOOT !!!");

	stack_check_init();

	trim_tune();

	cycle_leds();

	// route the external oscillator through the clock tree, per
	// the "clocking distribution tree" in the datasheet.
	CRY32CR = 0x9;

	uart_calibrate();

#if DEBUG_TRIM
	show_trim_dump();

	puts("dump:");
	trim_dump();
	show_trim_dump();
#endif

	adc_init();
	get_board_id();

	init_default_io();
	init_board_rev();	// Init various things based on board ID
	build_banner();

	event_init();
	pwm_init();
	timer_gpt3_init();
	ac_init();
	power_init_module();
	sdi_module_init();
#ifndef NO_PS2
	ps2_module_init();
	ps2_init();
#endif
	onewire_init_module();
	smbus_init_module();
	battery_init_module();

	if ( ISL_charger_init() )
	{
		unsigned char loop;
		for (loop=0; loop<15; loop++)
		{
			set_LED_CHG_RED_on();
			usec_delay(2*LED_CYCLE_DELAY);
			set_LED_CHG_RED_off();
			usec_delay(2*LED_CYCLE_DELAY);
		}
	}

	life_init_module();
	mppt_init_module();
	ols_init_module();

	tx_drain();

	sdi_deinit();   // will init at power-up time

	apply_max_power_limit();

	onewire_init_bus();
	onewire_enable();
	battery_start();
	ols_start();
#ifdef NO_PS2
	kbd_watch_init();
#endif
	enable_stop_mode(1);

	autopower_init();

	while (1)
	{
		every_process();
#ifdef EVENT_DEBUG
		event_process();
#endif
		stack_check();

		power_process();
		sdi_process();
		monitor();
		onewire_process();
		battery_process();
		mppt_process();
		ols_process();

#ifndef NO_PS2
		key_process();
		run_kbc_id();
		ps2_process();
		touchpad_process();
#else
		kbd_watch_process();
#endif

	}
}

